﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Muccu.Contracts;
using System.Reflection;
using System.Web;
using Muccu.JavaScript;

namespace Muccu
{
    internal class Dispatcher
    {
        private Type serviceType;

        private Dictionary<string, FuncDispatcher> dispatchercache;

        public Dispatcher(Type type)
        {
            serviceType = type;
            dispatchercache = new Dictionary<string, FuncDispatcher>();

            foreach (var mi in type.GetMethods(BindingFlags.Public | BindingFlags.Instance))
            {
                var methodinvoker = mi; // Get closure on the invoker

                var jsAttr = methodinvoker.GetCustomAttributes(typeof(JavaScriptAttribute), false);
                var funcName = (jsAttr.Length == 1) ? ((JavaScriptAttribute)jsAttr[0]).Name : methodinvoker.Name.ToCamelCase();

                // Security
                var secAttr = methodinvoker.GetCustomAttributes(typeof(RequiredRoleAttribute), false);
                var secFilter = secAttr.Length > 0 ? ((RequiredRoleAttribute)secAttr[0]).Role : null;

                var parameterInfos = methodinvoker.GetParameters();

                // Validation
                var validators = new Func<object, bool>[parameterInfos.Length];
                for (int i = 0; i < parameterInfos.Length; i++)
                {
                    var name = parameterInfos[i].Name;
                    var attr = parameterInfos[i].GetCustomAttributes(typeof(ArgumentValidationAttribute), false);
                    var parType = parameterInfos[i].ParameterType;
                    if (attr.Length == 0)
                    {
                        validators[i] = val => true; // TODO: Implement JS object validation
                    }
                    else
                    { 
                        validators[i] = val => ((ArgumentValidationAttribute)attr[0]).ServerValidate(val);
                    }
                }

                if (methodinvoker.ReturnType != typeof(void))
                {
                    dispatchercache.Add(funcName, (service, request) =>
                    {
                        string role = secFilter;
                        if (role != null && !HttpContext.Current.User.IsInRole(role))
                        {
                            HttpContext.Current.Response.StatusCode = 401;
                            return Response.E_401;
                        }
                        
                        // TODO: Should give some hint of what the error is...
                        if (!Validate(request.Parameters, validators))
                            return Response.E_32602;

                        return new Response
                        {
                            Id = request.Id,
                            Result = methodinvoker.Invoke(service, Request.ConvertParameters(parameterInfos, request.Parameters))
                        };
                    });
                }
                else // type void
                {
                    dispatchercache.Add(funcName, (service, request) =>
                    {
                        string role = secFilter;
                        if (role != null && !HttpContext.Current.User.IsInRole(role))
                        {
                            return Response.E_401;
                        }
                        if (!Validate(request.Parameters, validators))
                            return Response.E_32602;

                        methodinvoker.Invoke(service, Request.ConvertParameters(parameterInfos, request.Parameters));
                        return new Response
                        {
                            Id = request.Id,
                            Result = "completed"
                        };
                    });
                }
            }
        }

        private bool Validate(object[] values, Func<object, bool>[] validators)
        {
            // We don't have to check for equal length?
            for (int i = 0; i < values.Length; i++)
            {
                if (!validators[i](values[i]))
                    return false;
            }
            return true;
        }

        public Response Execute(JavaScriptService target, Request request)
        {
            if (!dispatchercache.ContainsKey(request.Method))
                throw new NotSupportedException(string.Format("Methodname: {0} not supported by service.", request.Method));
            if (target == null)
                throw new ArgumentNullException("target");

            Tracer.WriteMethodAndParmeters(request);
            return dispatchercache[request.Method](target, request);
        }

        public delegate Response FuncDispatcher(JavaScriptService service, Request request);
    }
}
